/*
 * TCSS 422
 * Project 2.
 */
import java.util.List;

import javax.swing.JTextArea;
/**
 * CPU.java, executes the currently running simulated porocess and handles low lvl OS task.
 * @author Levon Keichichian.
 * @author Khanh Nguyen
 * @author Thang Tran
 * @author Gian Bata.
 *
 */
public class CPU implements Runnable
{
	private ProcessControlBlock my_current_pcb;
	private List<ProcessControlBlock> my_pcb_list;
	private List<IODevice> my_device_list;
	private SystemTimer my_timer;
	private String my_current_instruction;
	private Scheduler my_scheduler;
	private JTextArea my_text_area;
	private volatile boolean my_done_flag;
	private int my_program_counter;
	
	public CPU(final String the_scheduling_policy, final List<ProcessControlBlock> the_pcb_list,
			final List<IODevice> the_device_list, final JTextArea the_text_area, final SystemTimer the_timer)
	{
		my_pcb_list = the_pcb_list;
		
		my_device_list = the_device_list;
		my_timer = new SystemTimer(0, "System Timer");
		my_device_list.add(0, my_timer);
		
		for (final IODevice device : my_device_list)
		{
			device.setCPU(this);
		}
		
		my_scheduler = new Scheduler(the_scheduling_policy, this);
		if (the_scheduling_policy.equals("Round-Robin"))
		{
			my_current_pcb = my_pcb_list.get(0);
			my_current_pcb.setProcessState("RUNNING");
		}
		else
		{
			my_current_pcb = my_pcb_list.get(my_scheduler.getNextPCB());
		}
		
		my_text_area = the_text_area;
		my_program_counter = 0;
		my_current_instruction = my_current_pcb.getProcess().getInstructions()[0];
		
		my_done_flag = true;
		
		printNextProcess();
		printProcessStates();
	}
	
	public void stop()
	{
		my_timer.stop();
		my_done_flag = false;
	}
	
	public List<ProcessControlBlock> getPCBList()
	{
		return my_pcb_list;
	}
	
	public synchronized void handleInterrupt(final IODevice the_device)
	{
		synchronized(my_text_area)
		{
			my_text_area.append("\n" + the_device.toString());
			
			if (!"System Timer".equals(the_device.getName()))
			{
				final ProcessControlBlock pcb = the_device.getPCB();
				pcb.setProcessState("RUNNABLE");
				pcb.setNextStep(pcb.getNextStep() + 1);
				printProcessStates();
			}
			else
			{
				switchProcess();
			}
		}
	}
	
	public synchronized void printNextProcess()
	{
		final StringBuffer str = new StringBuffer();
		str.append("\n\nScheduler: Running process ").append(my_current_pcb.getProcessId());
		str.append(" (").append(my_current_pcb.getProcessName());
		str.append(") next.").append("\n");
		
		synchronized(my_text_area)
		{
			my_text_area.append(str.toString());
			my_text_area.setCaretPosition(my_text_area.getText().length());
		}
	}
	
	public synchronized void printProcessStates()
	{
		final StringBuffer str = new StringBuffer();
		str.append("\n");
		
		for (int i = 0; i < my_pcb_list.size(); i++)
		{
			str.append("  Process ").append(my_pcb_list.get(i).getProcessId());
			str.append(" (").append(my_pcb_list.get(i).getProcessName()).append(")");
			str.append("\'s state : ").append(my_pcb_list.get(i).getProcessState()).append("\n");	
		}
		
		synchronized(my_text_area)
		{
			my_text_area.append(str.toString());
			my_text_area.setCaretPosition(my_text_area.getText().length());
		}
	}
	
	public int getProgramCounter()
	{
		return my_program_counter;
	}
	
	public void setProgramCounter(final int the_new_counter)
	{
		my_program_counter = the_new_counter;
	}
	
	public void resetCount()
	{
		my_program_counter = 0;
	}
	
	public void switchProcess()
	{
		if (!"BLOCKED".equals(my_current_pcb.getProcessState()))
		{	
			my_current_pcb.setProcessState("RUNNABLE");
		}	
		
		ProcessControlBlock next_pcb = my_pcb_list.get(my_scheduler.getNextPCB());
		
		while (next_pcb.getProcessState().equals("BLOCKED"))
		{
			next_pcb = my_pcb_list.get(my_scheduler.getNextPCB());
		}
		
		my_current_pcb = next_pcb;
		my_program_counter = my_current_pcb.getNextStep();
		my_current_pcb.setProcessState("RUNNING");
		printNextProcess();
		printProcessStates();
	}
	
	public void executeIOInstruction()
	{
		synchronized(my_text_area)
		{
			my_text_area.append("\nProcess " + my_current_pcb.getProcessId() + " (" 
				+ my_current_pcb.getProcessName() + ") made a system call " 
				+ "to device " + my_current_pcb.getDevice().getId() 
				+ " (" + my_current_pcb.getDevice().getName() + ").");
		}
		
		my_current_pcb.setProcessState("BLOCKED");
		
		my_current_pcb.setNextStep(my_program_counter);
		my_current_pcb.getDevice().notifyThread();
		
		switchProcess();
	}
	
	public void endOfProcessInstructions()
	{
		my_current_pcb.setNextStep(0);
		
		switchProcess();
	}
	
	public void fetchNextInstruction()
	{
		if (my_program_counter + 1 == my_current_pcb.getProcess().getNumInstructions())
		{
			resetCount();
		}
		else
		{
			my_program_counter++;
		}
		
		my_current_instruction = my_current_pcb.getProcess().getInstructions()[my_program_counter];
	}
	
	public void executeProduceInstruction()
	{
		final ProducerProcess producer = (ProducerProcess) my_current_pcb.getProcess();
		final SharedMemory buffer = producer.getBuffer();
		
		if (buffer.isFull())
		{
			my_text_area.append("\nProcess " + producer.getId() + " (" + producer.getName()
					+ ") attempted to increment memory location " + buffer.getId());
			my_current_pcb.setProcessState("BLOCKED");
			my_current_pcb.setNextStep(my_program_counter);
			switchProcess();
		}
		else
		{
			buffer.produce(buffer.getSize() + 1);
			my_text_area.append("\nProcess " + producer.getId() + " (" + producer.getName()
					+ ") incremented memory location " + buffer.getId());
			printNextProcess();
			printProcessStates();
			my_text_area.setCaretPosition(my_text_area.getText().length());
		}
	}
	
	public void executeConsumeInstruction()
	{
		final ConsumerProcess consumer = (ConsumerProcess) my_current_pcb.getProcess();
		final SharedMemory buffer = consumer.getBuffer();
		
		if (buffer.isEmpty())
		{
			my_text_area.append("\nProcess " + consumer.getId() + " (" + consumer.getName()
					+ ") attempted to decrement memory location " + buffer.getId());
			my_current_pcb.setProcessState("BLOCKED");
			my_current_pcb.setNextStep(my_program_counter);
			switchProcess();
		}
		else
		{
			buffer.consume();
			my_text_area.append("\nProcess " + consumer.getId() + " (" + consumer.getName()
					+ ") decremented memory location " + buffer.getId());
			printNextProcess();
			printProcessStates();
			my_text_area.setCaretPosition(my_text_area.getText().length());
		}
	}

	@Override
	public void run() 
	{
		while (my_done_flag)
		{
			try
			{
				if ("IO".equals(my_current_instruction))
				{
					executeIOInstruction();
				}
				else if ("PRODUCE".equals(my_current_instruction))
				{
					executeProduceInstruction();
				}
				else if ("CONSUME".equals(my_current_instruction))
				{
					executeConsumeInstruction();
				}
				else if (my_program_counter == my_current_pcb.getProcess().getNumInstructions())
				{
					endOfProcessInstructions();
				}
				
				Thread.sleep(1000);
				
				fetchNextInstruction();
			}
			catch (final InterruptedException the_exception)
			{
				System.out.println(getClass().getName() + " interrupted");
			}
		}
	}
}
